package cc.verywell.pureblock.util.sign;

import java.nio.ByteBuffer;
import java.security.DigestException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;

import com.sun.swing.internal.plaf.synth.resources.synth;

import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.util.EncryptUtil;
import cc.verywell.pureblock.util.HashNotEqualException;
import cc.verywell.pureblock.util.HashUtil;

/**
* 
* see https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyGenerator
* 
* @author TanYaqiu 
* @address flat 601
* @date 2019年2月25日 上午12:39:36
*/
public class Aes128 implements EncryptUtil {

	final private SecretKey key;
	final private Cipher encode,decode;
	final private HashUtil hash;
	final byte[] hashBuf;
	final byte[] hashEncodeBuf;
	final byte[] decodeBuf1;
	final byte[] decodeBuf2;
	public Aes128() {
		this(genKey());
	}
	public Aes128(byte[] key) {
		this(new SecretKeySpec(key,"AES"));
	}
	public Aes128(SecretKey skey) {
		this.key = skey;
		try {
			Cipher cipher;
			this.encode = cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 创建密码器
			cipher.init(Cipher.ENCRYPT_MODE, skey);// 初始化加密器
			this.decode = cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 创建密码器
			cipher.init(Cipher.DECRYPT_MODE, skey);// 初始化加密器
			this.hash = new MD5();
			this.hashBuf = new byte[hash.length()];
			this.hashEncodeBuf = new byte[this.encode.getOutputSize(this.hashBuf.length)];
			this.decodeBuf1 = new byte[this.hashEncodeBuf.length];
			this.decodeBuf2 = new byte[this.hashEncodeBuf.length];
		} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) {
			if(Pblog.INIT)Pblog.INIT(e);
			throw new RuntimeException(e);
		}
	}
	  
	/**
	 * 
	 * @return AES 密码
	 */
	private static SecretKey genKey() {
        KeyGenerator kgen;
		try {
			kgen = KeyGenerator.getInstance("AES");
		} catch (NoSuchAlgorithmException e) {
			if(Pblog.INIT)Pblog.INIT(e);
			throw new RuntimeException(e);
		}
        kgen.init(128, new SecureRandom(/*pkey.getBytes()*/));
        return kgen.generateKey(); 
 	}
	
	public synchronized int encode(ByteBuffer one) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException, DigestException {
		hash.hash(one,hashBuf);
		int h=0,s=0;
		try {
			try {
				s+= h = encode.update(hashBuf,0,hashBuf.length,hashEncodeBuf);
			}finally {
				s += encode.doFinal(one.array(), one.position()+one.arrayOffset(), one.remaining(), one.array(), one.position()+one.arrayOffset()+h);
			}
		}catch(ShortBufferException | IllegalBlockSizeException | BadPaddingException w) {
			//只要出问题就初始化，否则后面还要出问题;
			try {
				encode.init(Cipher.ENCRYPT_MODE, key);
				if(Pblog.INIT)Pblog.INIT("reset encode@",this.getClass());
			} catch (InvalidKeyException e) {
				if(Pblog.INIT)Pblog.INIT(e);
			}
			throw w;
		}
		one.limit(one.position()+s);
		if(h>0)
			System.arraycopy(hashEncodeBuf, 0, one.array(), one.position()+one.arrayOffset(), h);
		return s;
	}
	public synchronized int encode(ByteBuffer input,ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException, DigestException {
		hash.hash(input,hashBuf);
		int h = 0;
		try {
			try {
				h += encode.update(hashBuf,0,hashBuf.length,hashEncodeBuf);
				for (int i = 0; i < h; i++) {
					output.put(hashEncodeBuf[i]);
				}
			}finally {
				h += encode.doFinal(input, output);
			}
		}catch(ShortBufferException | IllegalBlockSizeException | BadPaddingException w) {
			//只要出问题就初始化，否则后面还要出问题;
			try {
				encode.init(Cipher.ENCRYPT_MODE, key);
				if(Pblog.INIT)Pblog.INIT("reset encode@",this.getClass());
			} catch (InvalidKeyException e) {
				if(Pblog.INIT)Pblog.INIT(e);
			}
			throw w;
		}
		return h;
	}
	/**
	 * 通过应原来大小，返回加密后大小;
	 */
	public synchronized int outputLength(int inputLength) {
		return encode.getOutputSize(inputLength+hashBuf.length);
	}
	
	public synchronized int decode(ByteBuffer one) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException, DigestException, HashNotEqualException {
		int p = one.position();
		int l = decodeBuf1.length>one.remaining()?one.remaining():decodeBuf1.length;
		one.get(decodeBuf1);
		int rp = one.position();
		int s = 0;
		try {
			try {
				int h = decode.update(decodeBuf1,0,l,decodeBuf2);//如果 解密后的文件 比 密文 还长这里会报 ShortBufferException，但AES不会
				if(h<hashBuf.length) {
					throw new HashNotEqualException("decode "+h+" byte, less than hash size "+hashBuf.length);
				}
				one.position(p);
				if(h>hashBuf.length) {
					for(int i=hashBuf.length;i<h;i++,s++) {
						one.put(decodeBuf2[i]);
					}
				}
			}finally {
				s += decode.doFinal(one.array(), rp+one.arrayOffset(), one.limit()-rp, one.array(), one.position()+one.arrayOffset());
			}
		}catch(ShortBufferException | IllegalBlockSizeException | BadPaddingException w) {
			//只要出问题就初始化，否则后面还要出问题;
			try {
				decode.init(Cipher.DECRYPT_MODE, key);
				if(Pblog.INIT)Pblog.INIT("reset decode@",this.getClass());
			} catch (InvalidKeyException e) {
				if(Pblog.INIT)Pblog.INIT(e);
			}
			throw w;
		}
		one.position(p);
		one.limit(p+s);
		hash.hash(one,decodeBuf1);
		one.position(p);
        //Pblog.printSECURE("[",decodeBuf1,"]!=[",decodeBuf2,"]");
        for (int i=0; i<hashBuf.length; i++)
            if (decodeBuf1[i] != decodeBuf2[i])
              	throw new HashNotEqualException("encrypt data was not equal hash !");
  		return s;
	}
	public synchronized int decode(ByteBuffer input,ByteBuffer output) throws ShortBufferException, IllegalBlockSizeException, BadPaddingException, HashNotEqualException, DigestException {
		int l = decodeBuf1.length>input.remaining()?input.remaining():decodeBuf1.length;
		input.get(decodeBuf1);
		int s = 0;
		try {
			try {
				int h = decode.update(decodeBuf1,0,l,decodeBuf2);
				if(h<hashBuf.length) {
					throw new HashNotEqualException("decode "+h+" byte, less than hash size "+hashBuf.length);
				}
				if(h>hashBuf.length)
					for(int i=hashBuf.length;i<h;i++,s++)
						output.put(decodeBuf2[i]);
			}finally {
				s+=decode.doFinal(input, output);
			}
		}catch(ShortBufferException | IllegalBlockSizeException | BadPaddingException w) {
			//只要出问题就初始化，否则后面还要出问题;
			try {
				decode.init(Cipher.DECRYPT_MODE, key);
				if(Pblog.INIT)Pblog.INIT("reset decode@",this.getClass());
			} catch (InvalidKeyException e) {
				if(Pblog.INIT)Pblog.INIT(e);
			}
			throw w;
		}
		int p = output.position();
		int e = output.limit();
		output.position(p-s);
		output.limit(p);
		hash.hash(output,decodeBuf1);
		output.limit(e);
		output.position(p);
		//Pblog.printSECURE(l,"->",h,"->",hashBuf.length,"[",decodeBuf1,"]!=[",decodeBuf2,"]");
        for (int i=0; i<hashBuf.length; i++)
            if (decodeBuf1[i] != decodeBuf2[i])
            	throw new HashNotEqualException("encrypt data was not equal hash !");
		return s;
	}
	
	public SecretKey getKey() {
		return key;
	}

}
